var aetas = {};

//(function() {

    aetas.now = function() {
        return new Date();
    };

    function Term(_pattern, _value) { 
        this.pattern = _pattern;
        this.value = _value;
    };
    
    function Nonterm() {
        this.symbols = arguments;

        this.composeCache = null;
        
        this.compose = function() {
            if (this.composeCache) {
                return this.composeCache;
            }
            return this.composeCache = map(
                function(prod) {
                    if (prod instanceof Nonterm || prod instanceof Expression) { 
                        return prod.compose();
                    }
                    else {
                        return [
                            "(",
                            map(
                                function(t) {
                                    if (t instanceof Nonterm || t instanceof Expression) { 
                                        return t.compose();
                                    }
                                    else if (t instanceof Term) {
                                        return t == aetas.grammar.terminals.lambda ?
                                            null :
                                            [ "(\\b(", t.pattern, ")\\b)" ].join("");
                                    }
                                    else {
                                        return "";
                                    }
                                },
                                prod
                            ).join("|"),
                            inArray(aetas.grammar.terminals.lambda, prod) ? 
                                ")?" : 
                                ")"
                        ].join("");
                    }
                },
                this.symbols
            ).join("\\s*");
        };
    }
    
    function Expression(_symbols, _parser) {
        this.symbols = _symbols;
        
        this.parse = _parser;

        this.composeCache = null;
        
        this.compose = function() {
            if (this.composeCache) {
                return this.composeCache;
            }

            return this.composeCache = new Nonterm(this.symbols).compose();
        }
    }
    
    aetas.ParseResult = function(_type) {
        this.type = _type;
    
        if (_type == "error") {
            this.message = arguments[1];
        }
        
        else if (_type == "date" || _type == "time" || _type == "datetime") {
            this.date = arguments[1];
        }
        
        else if (_type == "daterange") {
            this.from = arguments[1];
            this.to = arguments[2];
        }
        
        this.toString = function() {
            if (this.type == "error") {
                return [ "Error: ", this.message ].join("");
            }
            else if (this.type == "date") {
                return this.date.toDateString();
            }
            else if (this.type == "time") {
                return this.date.toTimeString();
            }
            else if (this.type == "datetime") {
                return this.date.toString();
            }
            else if (this.type == "daterange") {
                return [ "From: ", this.from.toDateString(), " To: ", this.to.toDateString() ].join("");
            }
        };
    };

    aetas.grammar = { };
    aetas.grammar.terminals = { };
    aetas.grammar.nonterminals = { };
    aetas.grammar.expressions = { };
    
    aetas.grammar.terminals.lambda = new Term("");

    aetas.grammar.terminals.daysOfTheWeek = [
        new Term("(sunday)|(sun)", 0),
        new Term("(monday)|(mon)", 1),
        new Term("(tuesday)|(tues?)", 2), 
        new Term("(wednesday)|(wed)", 3),
        new Term("(thursday)|(thu(rs?)?)", 4), 
        new Term("(friday)|(fri)", 5), 
        new Term("(saturday)|(sat)", 6)
    ];

    aetas.grammar.terminals.namesOfTheMonths = [
        new Term("(january)|(jan)", 0),
        new Term("(february)|(feb)", 1), 
        new Term("(march)|(mar)", 2),
        new Term("(april)|(apr)", 3),
        new Term("may", 4),
        new Term("(june)|(jun)", 5),
        new Term("(july?)", 6),
        new Term("(august)|(aug)", 7), 
        new Term("(september)|(sept?)", 8), 
        new Term("(october)|(oct)", 9),
        new Term("(november)|(nov)", 10),
        new Term("(december)|(dec)", 11) 
    ];

    aetas.grammar.terminals.numbersOfDays = [
        new Term("(thirty-((first)|(one)))|(31(st)?)", 31),
        new Term("((thirtieth)|(thirty))|(30(th)?)", 30),
        new Term("(twenty-((ninth)|(nine)))|(29(th)?)", 29),
        new Term("(twenty-eighth?)|(28(th)?)", 28),
        new Term("(twenty-seven(th)?)|(27(th)?)", 27),
        new Term("(twenty-six(th)?)|(26(th)?)", 26),
        new Term("(twenty-((fifth)|(five)))|(25(th)?)", 25),
        new Term("(twenty-four(th)?)|(24(th)?)", 24),
        new Term("(twenty-((third)|(three)))|(23(rd)?)", 23),
        new Term("(twenty-((second)|(two)))|(22(nd)?)", 22),
        new Term("(twenty-((first)|(one)))|(21(st)?)", 21),
        new Term("(twenty)|(twentieth)|(20(th)?)", 20),
        new Term("(ninteen(th)?)|(19(th)?)", 19),
        new Term("(eighteen(th)?)|(18(th)?)", 18),
        new Term("(seventeen(th)?)|(17(th)?)", 17),
        new Term("(sixteen(th)?)|(16(th)?)", 16),
        new Term("(fifteen(th)?)|(15(th)?)", 15),
        new Term("(fourteen(th)?)|(14(th)?)", 14),
        new Term("(thirtheen(th)?)|(13(th)?)", 13),
        new Term("(twelve)|(twelfth)|(12(th)?)", 12),
        new Term("(eleven(th)?)|(11(th)?)", 11),
        new Term("(ten(th)?)|(10(th)?)", 10),
        new Term("(nine)|(ninth)|(0?9(th)?)", 9),
        new Term("(eighth?)|(0?8(th)?)", 8),
        new Term("(seven(th)?)|(0?7(th)?)", 7),
        new Term("(six(th)?)|(0?6(th)?)", 6),
        new Term("(five)|(fifth)|(0?5(th)?)", 5),
        new Term("(four(th)?)|(0?4(th)?)", 4),
        new Term("(three)|(third)|(0?3(rd)?)", 3),
        new Term("(two)|(second)|(0?2(nd)?)", 2),
        new Term("(one)|(first)|(0?1(st)?)", 1)
    ];
        
    aetas.grammar.terminals.dateExpression = new Term("(([01]?\\d)[-/]([012]?\\d|30|31)[-/]\\d{1,4})");

    aetas.grammar.terminals.yearlessDateExpression = new Term("(([01]?\\d)[-/]([012]?\\d|30|31))");
        
    aetas.grammar.terminals.relativeDay = [
        new Term("(the\\s+)?day\\s+before\\s+yesterday", -2),
        new Term("(the\\s+)?day\\s+after\\s+tomorrow", 2),
        new Term("yesterday", -1),
        new Term("tomorrow", 1),
        new Term("today", 0)
    ];
        
    aetas.grammar.terminals.year = new Term("(\\d{1,4})");
        
    aetas.grammar.terminals.fullTime = new Term("(([01]?\\d)|(2[1234])):(([012345]\\d))(:([012345]\\d))?");
        
    aetas.grammar.terminals.hour12 = [
        new Term("(one)|1", 1),
        new Term("(two)|2", 2),
        new Term("(three)|3", 3),
        new Term("(four)|4", 4),
        new Term("(five)|5", 5),
        new Term("(six)|6", 6),
        new Term("(seven)|7", 7),
        new Term("(eight)|8", 8),
        new Term("(nine)|9", 9),
        new Term("(ten)|10", 10),
        new Term("(eleven)|11", 11),
        new Term("(twelve)|12", 12)
    ];

    aetas.grammar.nonterminals.relativeDay = new Nonterm(
        aetas.grammar.terminals.relativeDay
    );

    aetas.grammar.nonterminals.nameOfMonth = new Nonterm(
        aetas.grammar.terminals.namesOfTheMonths
    );

    aetas.grammar.nonterminals.numberOfDay = new Nonterm(
        [ new Term("the"), aetas.grammar.terminals.lambda ],
        aetas.grammar.terminals.numbersOfDays
    );

    aetas.grammar.nonterminals.dayOfWeek = new Nonterm(
        aetas.grammar.terminals.daysOfTheWeek.concat([ 
            aetas.grammar.terminals.lambda
        ])
    );

    aetas.grammar.nonterminals.dayOfWeekNonOptional = new Nonterm(
        aetas.grammar.terminals.daysOfTheWeek
    );

    aetas.grammar.nonterminals.dateExpression = new Nonterm(
        [ aetas.grammar.terminals.dateExpression ]
    );

    aetas.grammar.nonterminals.yearlessDateExpression = new Nonterm(
        [ aetas.grammar.terminals.yearlessDateExpression ]
    );

    aetas.grammar.nonterminals.thisMonth = new Nonterm(
        aetas.grammar.nonterminals.dayOfWeek,
        [ new Term("the") ],
        new Nonterm(aetas.grammar.terminals.numbersOfDays)
    );

    aetas.grammar.nonterminals.formalDay = new Nonterm(
        aetas.grammar.nonterminals.dayOfWeek,
        aetas.grammar.nonterminals.numberOfDay,
        [ new Term("of") ],
        aetas.grammar.nonterminals.nameOfMonth,
        [ aetas.grammar.terminals.year, aetas.grammar.terminals.lambda ]
    );

    aetas.grammar.nonterminals.informalDay = new Nonterm(
        aetas.grammar.nonterminals.dayOfWeek,
        aetas.grammar.nonterminals.nameOfMonth,
        [ new Term("the"), aetas.grammar.terminals.lambda ],
        aetas.grammar.nonterminals.numberOfDay,
        [ new Term("the"), aetas.grammar.terminals.lambda ],
        [ aetas.grammar.terminals.year, aetas.grammar.terminals.lambda ]
    );

    aetas.grammar.nonterminals.sequentialDay = new Nonterm(
        [ new Term("(next)|(last)|(this)"), ],
        aetas.grammar.nonterminals.dayOfWeekNonOptional
    );

    aetas.grammar.nonterminals.hourWithPeriod = new Nonterm(
        [ new Term("at"), aetas.grammar.terminals.lambda ],
        aetas.grammar.terminals.hour12.concat(
            new Term("([0]?\\d)|(1[12])")
        ),
        [ new Term("o'?clock"), aetas.grammar.terminals.lambda ],
        [ new Term("[ap]\\.?m\\.?"), aetas.grammar.terminals.lambda ]
    );

    aetas.grammar.nonterminals.fullTime = new Nonterm(
        [ new Term("at"), aetas.grammar.terminals.lambda ],
        [ aetas.grammar.terminals.fullTime ]
    );

    aetas.grammar.nonterminals.dailyTimeRange = new Nonterm( 
        [ new Term("today"), aetas.grammar.terminals.lambda ], 
        [ new Term("from"), aetas.grammar.terminals.lambda ], 
        [ aetas.grammar.expressions.time ],
        [ new Term("to") ], 
        [ aetas.grammar.expressions.time ]
    );

    aetas.grammar.expressions.date = new Expression([
            aetas.grammar.nonterminals.relativeDay,
            aetas.grammar.nonterminals.sequentialDay,
            aetas.grammar.nonterminals.formalDay,
            aetas.grammar.nonterminals.thisMonth,
            aetas.grammar.nonterminals.informalDay,
            aetas.grammar.nonterminals.dateExpression,
            aetas.grammar.nonterminals.yearlessDateExpression
        ],
        function(san) {
            console.log(["Started parsing ", input, "as a date"].join(""));
        
            if (nontermIsMatch(aetas.grammar.nonterminals.dateExpression, san)) {
                return new aetas.ParseResult(
                    "date",
                    new Date(san)
                );
            }

            else if (nontermIsMatch(aetas.grammar.nonterminals.dayOfWeek, san)) {
                var 
                    dayOfWeek = parseNamedDay(san),
                    currentDay = new Date().getDay(),
                    result = aetas.now();
                                
                result.setDate(
                    result.getDate() + (dayOfWeek - currentDay) + 
                    (currentDay >= dayOfWeek ? 7 : 0)
                );
                
                return new aetas.ParseResult(
                    "date",
                    result
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.sequentialDay, san)) {
                var 
                    terms = san.split(/\b\s+\b/g),
                    dayOfWeek = parseNamedDay(terms[1]),
                    currentDay = new Date().getDay(),
                    result = aetas.now();
                
                if (terms[0] == "this" || terms[0] == "next") {
                    
                    result.setDate(
                        result.getDate() + (dayOfWeek - currentDay) + 
                        (currentDay >= dayOfWeek ? 7 : 0)
                    );
                    
                    return new aetas.ParseResult(
                        "date",
                        result
                    );
                }
                
                else {
                    result.setDate(
                        result.getDate() - (currentDay - dayOfWeek) -
                        (currentDay <= dayOfWeek ? 7 : 0)
                    );
                    
                    return new aetas.ParseResult(
                        "date",
                        result
                    );
                }
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.yearlessDateExpression, san)) {
                var d = new Date(san);
                
                d.setFullYear(new Date().getFullYear());
                
                if (d.getMonth() < new Date().getMonth()) {
                    d.setFullYear(d.getFullYear() + 1);
                }
                
                return new aetas.ParseResult(
                    "date",
                    d
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.relativeDay, san)) {
                var 
                    d = aetas.now(),
                    off = valueInTerminalSet(aetas.grammar.terminals.relativeDay, san);
                    
                d.setDate(d.getDate() + off);

                return new aetas.ParseResult(
                    "date",
                    d
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.formalDay, san)) {
                var terms = san.split(/\b\s+\b/g);
                
                var parseLoc = 0;
                var dayOfWeek = parseNamedDay(terms[parseLoc]);
                
                if (!isNaN(dayOfWeek)) {
                    ++parseLoc;
                }
                
                if (terms[parseLoc] == "the") {
                    ++parseLoc;
                }
                
                var day = parseNunberedDay(terms[parseLoc]);
                
                parseLoc += 2;
                
                var month = parseNamedMonth(terms[parseLoc]);
                ++parseLoc;
                
                var year;
                
                if (parseLoc == terms.length || isNaN(parseYear(terms[parseLoc]))) {
                    year = new Date().getFullYear();
                }
                
                else {
                    year = parseYear(terms[parseLoc]);
                }
                
                var result = new Date(year, month, day);
                
                if (!isNaN(dayOfWeek) && dayOfWeek != result.getDay()) {
                    return new aetas.ParseResult(
                        "error",
                        "Day of week inconsistent with rest of date."
                    );
                }
                
                return new aetas.ParseResult(
                    "date",
                    result
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.informalDay, san)) {
                var terms = san.split(/\b\s+\b/g);
                
                var parseLoc = 0;
                
                var dayOfWeek = parseNamedDay(terms[parseLoc]);
                
                if (!isNaN(dayOfWeek)) {
                    ++parseLoc;
                }
                
                var month = parseNamedMonth(terms[parseLoc]);
                ++parseLoc;
                
                if (terms[parseLoc] == "the") {
                    ++parseLoc;
                }
                
                var day = parseNunberedDay(terms[parseLoc]);
                ++parseLoc;
                
                if (terms[parseLoc] == "of") {
                    ++parseLoc;
                }
                
                var year;
                
                if (parseLoc == terms.length || isNaN(parseYear(terms[parseLoc]))) {
                    year = new Date().getFullYear();
                }
                
                else {
                    year = parseYear(terms[parseLoc]);
                }
                
                var result = new Date(year, month, day);
                
                if (!isNaN(dayOfWeek) && dayOfWeek != result.getDay()) {
                    return new aetas.ParseResult(
                        "error",
                        "Day of week inconsistent with rest of date."
                    );
                }
                
                return new aetas.ParseResult(
                    "date",
                    result
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.thisMonth, san)) {
                var terms = san.split(/\b\s+\b/g);
                
                var parseLoc = 0;
                var dayOfWeek = parseNamedDay(terms[parseLoc]);
                
                if (!isNaN(dayOfWeek)) {
                    ++parseLoc;
                }
                
                ++parseLoc;
                
                var day = parseNunberedDay(terms[parseLoc]);
                
                var monthOffset = 0;
                
                if (day < aetas.now().getDate()) {
                    monthOffset = 1;
                }
                
                var result = new Date(aetas.now().getFullYear(), aetas.now().getMonth() + monthOffset, day);
                
                if (!isNaN(dayOfWeek) && dayOfWeek != result.getDay()) {
                    return new aetas.ParseResult(
                        "error",
                        "Day of week inconsistent with rest of date."
                    );
                }
                
                return new aetas.ParseResult(
                    "date",
                    result
                );
            }
            
            return new aetas.ParseResult(
                "error",
                "Parser dispairity."
            );
        }
    );

    aetas.grammar.expressions.time = new Expression([
            aetas.grammar.nonterminals.fullTime,
            aetas.grammar.nonterminals.hourWithPeriod
        ],
        function(input) {
            console.log(["Started parsing ", input, "as a time"].join(""));
            
            if (nontermIsMatch(aetas.grammar.nonterminals.hourWithPeriod, input)) {
                var 
                    terms = input.split(/\b\s+\b/g),
                    parseLoc = 0,
                    now = aetas.now(),
                    result = new Date();
                
                if (terms[parseLoc] == "at") {
                    ++parseLoc;
                }

                var hour = parseHour(terms[parseLoc]);
                
                if (isNaN(hour) && /^([0]?\\d)|(1[12])$/.test(terms[parseLoc])) {
                    hour = parseInt(terms[parseLoc]);
                }

                result.setMinutes(0);
                result.setHours(hour);
                result.setSeconds(0);
                
                return new aetas.ParseResult(
                    "time",
                    result
                );
            }
            
            else if (nontermIsMatch(aetas.grammar.nonterminals.fullTime, input)) {
                var 
                    terms = input.split(/\b\s+\b/g),
                    parseLoc = 0;
                
                if (terms[parseLoc] == "at") {
                    ++parseLoc;
                }

                var 
                    components = terms[parseLoc].split(/:/),
                    hour = components[0],
                    minute = components[1],
                    second = components.length > 2 ? components[2] : 0;
                    
                var time = new Date();

                time.setHours(hour);
                time.setMinutes(minute);
                time.setSeconds(second);
                
                return new aetas.ParseResult(
                    "time",
                    time
                );
            }
            
            return new aetas.ParseResult(
                "error",
                "Parser dispairity."
            );
        }
    );

    aetas.grammar.expressions.dateTime = new Expression([
            new Nonterm( 
                aetas.grammar.expressions.time, 
                [ new Term("on"), aetas.grammar.terminals.lambda ], 
                aetas.grammar.expressions.date 
            ),
            new Nonterm( 
                [ new Term("on"), aetas.grammar.terminals.lambda ],
                aetas.grammar.expressions.date,
                aetas.grammar.expressions.time 
            )
        ],
        function(input) {
            console.log(["Started parsing ", input, "as a dateTime"].join(""));
        
            var 
                regDate = new RegExp(aetas.grammar.expressions.date.compose()),
                regTime = new RegExp(aetas.grammar.expressions.time.compose()),

                dateSub = input.match(regDate)[0],
                timeSub = input.match(regTime)[0],

                dateRes = aetas.grammar.expressions.date.parse(dateSub.replace(/(^\s+)|(\s+$)/g, "")),
                timeRes = aetas.grammar.expressions.time.parse(timeSub.replace(/(^\s+)|(\s+$)/g, "")),

                result = dateRes.date;

            if (dateRes.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    dateRes.message
                );
            }

            else if (timeRes.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    timeRes.message
                );
            }

            else {

                result.setHours(timeRes.date.getHours());
                result.setMinutes(timeRes.date.getMinutes());
                result.setSeconds(timeRes.date.getSeconds());

                return new aetas.ParseResult(
                    "datetime",
                    result
                );
            }
        }
    );

    aetas.grammar.expressions.dateRange = new Expression([
            new Nonterm( 
                [ new Term("from"), aetas.grammar.terminals.lambda ], 
                aetas.grammar.expressions.date, 
                [ new Term("to") ], 
                aetas.grammar.expressions.date 
            )
        ],
        function(input) {
            console.log(["Started parsing ", input, "as a dateRange"].join(""));
        
            var 
                regDate = new RegExp(aetas.grammar.expressions.date.compose()),
                
                matches = input.match(regDate),

                stFrom = matches[0],
                stTo = matches[1],
                
                dtFrom = aetas.grammar.expressions.date.parse(stFrom),
                dtTo = aetas.grammar.expressions.date.parse(stTo);
                
            window.interest = { reg:regDate, inp:input };

            if (dtFrom.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    dtFrom.message
                );
            }

            else if (dtTo.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    dtTo.messagewhoop 
                );
            }

            else {
                return new aetas.ParseResult(
                    "daterange",
                    dtFrom.date,
                    dtTo.date
                );
            }
        }
    );

    aetas.grammar.expressions.timeRange = new Expression([
            aetas.grammar.nonterminals.dailyTimeRange
        ],
        function(input) {
            console.log(["Started parsing ", input, "as a timeRange"].join(""));
        
            var 
                regTime = new RegExp(aetas.grammar.expressions.time.compose()),

                stFrom = input.match(regTime)[0],
                stTo = input.substring(stFrom.length, input.length).match(regTime)[0],
                
                dtFrom = aetas.grammar.expressions.time.parse(stFrom),
                dtTo = aetas.grammar.expressions.time.parse(stTo);

            if (dtFrom.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    dtFrom.message
                );
            }

            else if (dtTo.type == "error") {
                return new aetas.ParseResult(
                    "error",
                    dtTo.message
                );
            }

            else {
                return new aetas.ParseResult(
                    "daterange",
                    dtFrom.date,
                    dtTo.date
                );
            }
        }
    );
    
    aetas.parse = function(input) {
        var san = input
            .replace(/[.,]/g, '')
            .replace(/(^\s+)|(\s+$)/g, "")
            .toLowerCase();
        
        if (!san.length) {
            return new aetas.ParseResult(
                "error",
                "No input."
            );
        }
        
        if (nontermIsMatch(aetas.grammar.expressions.dateRange, san)) {
            console.log([ "\"", san, "\" matched dateRange expression" ].join(''));
            return aetas.grammar.expressions.dateRange.parse(san);
        }

        else if (nontermIsMatch(aetas.grammar.expressions.dateTime, san)) {
            console.log([ "\"", san, "\" matched dateTime expression" ].join(''));
            return aetas.grammar.expressions.dateTime.parse(san);
        }
        
        else if (nontermIsMatch(aetas.grammar.expressions.date, san)) {
            console.log([ "\"", san, "\" matched date expression" ].join(''));
            return aetas.grammar.expressions.date.parse(san);
        }
        
        else if (nontermIsMatch(aetas.grammar.expressions.time, san)) {
            console.log([ "\"", san, "\" matched time expression" ].join(''));
            return aetas.grammar.expressions.time.parse(san);
        }

        else if (nontermIsMatch(aetas.grammar.expressions.timeRange, san)) {
            console.log([ "\"", san, "\" matched time expression" ].join(''));
            return aetas.grammar.expressions.timeRange.parse(san);
        }
        
        console.log([ "\"", san, "\" did not match an expression" ].join(''));
        
        return new aetas.ParseResult(
            "error",
            "Total jibberish."
        );
    };
    
    function nontermIsMatch(nonterm, input) {
        var reg = new RegExp(anchor(nonterm.compose()));
        
        var ret = reg.test(input) && (input.match(reg) || [ "" ] )[0].length == input.length;
        
        if (ret) {
            console.log([
                "\"",
                input,
                "\" matched a nonterminal"
            ].join(''));
        }
        
        return ret;
    }
    
    function valueInTerminalSet(set, input) {
        for (var idx = 0; idx < set.length; ++idx) {
            if (new RegExp(anchor(set[idx].pattern)).test(input)) {
                return set[idx].value;
            }
        }
        return Number.NaN;
    }
    
    function parseNamedDay(input) {
        return valueInTerminalSet(aetas.grammar.terminals.daysOfTheWeek, input);
    }
    
    function parseNunberedDay(input) {
        return valueInTerminalSet(aetas.grammar.terminals.numbersOfDays, input);
    }
    
    function parseNamedMonth(input) {
        return valueInTerminalSet(aetas.grammar.terminals.namesOfTheMonths, input);
    }
    
    function parseHour(input) {
        return valueInTerminalSet(aetas.grammar.terminals.hour12, input);
    }
    
    function parseYear(input) {
        if (new RegExp(aetas.grammar.terminals.year.pattern).test(input)) {
            return parseInt(input);
        }
        
        else {
            return Number.NaN;
        }
    }

    function map(f, a) {
        var ret = [], t;
        
        if (!a) {
            var omgwtfstopeverything = 4.5;
        }
        
        for (var idx = 0; idx < a.length; ++idx) {
            t = f(a[idx]);
            if (t != null) {
                ret.push(t);
            }
        }
        return ret;
    }
    
    function inArray(v, a) {
        for (var idx = 0; idx < a.length; ++idx) {
            if (a[idx] == v) {
                return true;
            }
        }
        return false;
    }
    
    function anchor(r) {        
        return r;
        //return [ "^", r, "$" ].join("");
    }
    
    // Borrowed from jQuery:
    function isFunction() {
        return toString.call(obj) === "[object Function]";
    }
    
    /*Object.prototype.show = function() {
        var props = [];
    
        for (var name in this) {
            if (this.hasOwnProperty(name)) {
                props.push(name);
            }
        }
        
        return props;
    };*/

//})();
